﻿using System;
using System.Collections.Generic;
using CacheNaga.Core;

namespace CacheNaga
{
	/// <summary>
	/// 提供对memcached的操作
	/// </summary>
	public sealed class MemcachedManager : CacheManagerBase
	{
		private readonly MemcachedClient client;
		
		#region .ctor

		/// <summary>
		/// 创建新的MemcachedClient实例
		/// </summary>
		/// <param name="instanceName">配置实例名称</param>
		public MemcachedManager(String instanceName)
		{
			client = MemcachedClient.GetInstance(instanceName);
		}

		/// <summary>
		/// 创建新的MemcachedClient实例
		/// </summary>
		/// <param name="instanceName">配置实例名称</param>
		/// <param name="keyPrefix">缓存key的前缀</param>
		public MemcachedManager(String instanceName, String keyPrefix)
		{
			client = MemcachedClient.GetInstance(instanceName);
			client.KeyPrefix = keyPrefix;
		}

		public MemcachedManager(String instanceName, String keyPrefix, uint compressionThreshold, uint minPoolSize, uint maxPoolSize, int sendReceiveTimeout, TimeSpan SocketRecycleAge)
		{
			client = MemcachedClient.GetInstance(instanceName);
			client.CompressionThreshold = compressionThreshold;
			client.KeyPrefix = keyPrefix;
			client.MaxPoolSize = maxPoolSize;
			client.MinPoolSize = minPoolSize;
			client.SendReceiveTimeout = sendReceiveTimeout;
			client.SocketRecycleAge = SocketRecycleAge;
		}

		#endregion

		#region 覆写的方法

		/// <summary>
		/// 添加或覆盖缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		public sealed override void Set(String key, Object value)
		{
			CheckKey(key);
			client.Set(key, value);
		}

		/// <summary>
		/// 添加或覆盖缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration"><see cref="System.DateTime"/>过期时间</param>
		public sealed override void Set(String key, Object value, DateTime absoluteExpiration)
		{
			Set(key, value, new TimeSpan((absoluteExpiration-DateTime.Now).Ticks));
		}

		/// <summary>
		/// 添加或覆盖缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration">相对过期时间</param>
		public sealed override void Set(String key, Object value, TimeSpan absoluteExpiration)
		{
			base.CheckKey(key);
			client.Set(key, value, absoluteExpiration);
		}

		/// <summary>
		/// 添加或覆盖缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="slidingExpiration">滑动过期时间</param>
		/// <remarks>
		/// 当我们需要滑动过期的缓存项时,请考虑该方式是否适合使用Memcached
		/// </remarks>
		public sealed override void SetSlidingExpiration(String key, Object value, DateTime slidingExpiration)
		{
			throw new NotSupportedException("该Memcached客户端不支持滑动过期");
		}

		/// <summary>
		/// 设置滑动过期缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration">滑动过期时间</param>
		/// <remarks>
		/// 当我们需要滑动过期的缓存项时,请考虑该方式是否适合使用Memcached
		/// </remarks>
		public sealed override void SetSlidingExpiration(String key, Object value, TimeSpan absoluteExpiration)
		{
			throw new NotSupportedException("该Memcached客户端不支持滑动过期");
		}

		/// <summary>
		/// 如果缓存不存在则添加缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		public sealed override void Add(String key, Object value)
		{
			base.CheckKey(key);
			client.Add(key, value);
		}

		/// <summary>
		/// 如果缓存不存在则添加缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration">过期时间</param>
		public sealed override void Add(String key, Object value, DateTime absoluteExpiration)
		{
			Add(key, value, new TimeSpan((absoluteExpiration-DateTime.Now).Ticks));
		}

		/// <summary>
		/// 如果缓存不存在则添加缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration">过期时间</param>
		public sealed override void Add(String key, Object value, TimeSpan absoluteExpiration)
		{
			base.CheckKey(key);
			client.Add(key, value, absoluteExpiration);
		}

		/// <summary>
		/// 如果缓存不存在则添加缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="slidingExpiration">滑动过期时间</param>
		/// <remarks>
		/// 当我们需要滑动过期的缓存项时,请考虑该方式是否适合使用Memcached
		/// </remarks>
		public sealed override void AddSlidingExpiration(String key, Object value, DateTime slidingExpiration)
		{
			throw new NotSupportedException("该Memcached客户端不支持滑动过期");
		}

		/// <summary>
		/// 如果缓存不存在则添加缓存
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="slidingExpiration"></param>
		/// <remarks>
		/// 当我们需要滑动过期的缓存项时,请考虑该方式是否适合使用Memcached
		/// </remarks>
		public sealed override void AddSlidingExpiration(String key, Object value, TimeSpan slidingExpiration)
		{
			throw new NotSupportedException("该Memcached客户端不支持滑动过期");
		}

		/// <summary>
		/// 仅当缓存项存在时替换缓存,否则不做操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		public sealed override void Replace(String key, Object value)
		{
			base.CheckKey(key);
			client.Replace(key, value);
		}

		/// <summary>
		/// 仅当缓存项存在时替换缓存,否则不做操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration">缓存过期时间</param>
		public sealed override void Replace(String key, Object value, DateTime absoluteExpiration)
		{
			client.Replace(key,value, new TimeSpan((absoluteExpiration-DateTime.Now).Ticks));
		}

		/// <summary>
		/// 仅当缓存项存在时替换缓存,否则不做操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <param name="absoluteExpiration">缓存过期时间</param>
		public sealed override void Replace(String key, Object value, TimeSpan absoluteExpiration)
		{
			base.CheckKey(key);
			client.Replace(key, value, absoluteExpiration);
		}

		/// <summary>
		/// 获取缓存内容
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <returns>缓存内容</returns>
		public sealed override Object Get(String key)
		{
			base.CheckKey(key);
			return client.Get(key);
		}

		/// <summary>
		/// 移除缓存内容
		/// </summary>
		/// <param name="key">缓存key</param>
		public sealed override void Remove(String key)
		{
			base.CheckKey(key);
			client.Delete(key);
		}

		/// <summary>
		/// 是否存在缓存项
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <returns>true表示存在</returns>
		/// <remarks>
		/// 不建议在memcached中使用contains操作
		/// </remarks>
		public sealed override bool Contains(String key)
		{
			return Get(key) != null;
		}

		#endregion

		#region 基于Memcached特性的扩展操作

		/// <summary>
		/// 删除缓存内容,并且在delay的时间段内不响应add/replace操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="delay"><see cref="DateTime"/>禁止操作的时间</param>
		public void Remove(String key, DateTime delay)
		{
			base.CheckKey(key);
			client.Delete(key, new TimeSpan(delay.Ticks));
		}

		/// <summary>
		/// 删除缓存内容,并且在delay的时间段内不响应add/replace操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="delay"><see cref="TimeSpan"/>禁止操作的时间</param>
		public void Remove(String key, TimeSpan delay)
		{
			base.CheckKey(key);
			client.Delete(key, delay);
		}

		/// <summary>
		/// 传递key集合,返回Object数组
		/// </summary>
		/// <param name="keys"></param>
		/// <returns></returns>
		/// <remarks>
		/// 暂时未支持泛型方式
		/// </remarks>
		public Object[] Get(List<String> keys)
		{
			keys.ForEach(key => base.CheckKey(key));
			return client.Get(keys.ToArray());
		}

		/// <summary>
		/// 根据key批量获取缓存,并返回缓存中为null的key集合
		/// </summary>
		/// <param name="keys"></param>
		/// <param name="NullValueKeys">缓存中为null的key集合</param>
		/// <returns></returns>
		public Object[] Get(List<String> keys, out List<String> NullValueKeys)
		{
			var results = Get(keys);
			NullValueKeys = new List<string>();
			for (var i = 0; i < results.Length; i++)
			{
				if (results[i] == null)
					NullValueKeys.Add(keys[i]);
			}
			return results;
		}

		/// <summary>
		/// 根据key批量获取缓存,如果值不存在则
		/// </summary>
		/// <param name="keys"></param>
		/// <param name="getFromSourceIfNull"></param>
		/// <returns></returns>
		public Object[] Get(List<String> keys, Func<List<String>, List<Object>> getFromSourceIfNull)
		{
			List<string> nullKeys;
			//从Memcached里获取到的集合
			var res = Get(keys, out nullKeys);
			if (nullKeys != null && nullKeys.Count > 0)
			{
				List<object> otherSource = getFromSourceIfNull(nullKeys);

				if (otherSource != null && otherSource.Count > 0)
				{
					Queue<object> queue = new Queue<object>(otherSource.Count);
					Queue<string> keyQueue = new Queue<string>(nullKeys.Count);

					otherSource.ForEach(queue.Enqueue);
					nullKeys.ForEach(keyQueue.Enqueue);

					for (int i = 0; i < res.Length; i++)
					{
						if (res[i] == null)
						{
							object obj = queue.Dequeue();
							string key = keyQueue.Dequeue();
							res[i] = obj;
							//添加到缓存里
							client.Add(key, obj);
						}
					}
				}
			}

			return res;
		}

		/// <summary>
		/// 初始化计数器,并设置初始值为0
		/// </summary>
		/// <param name="key">缓存key</param>
		public void SetCounter(String key)
		{
			SetCounter(key, 0UL);
		}

		/// <summary>
		/// 设置计数器,并赋予初始值
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">初始值</param>
		public void SetCounter(String key, UInt64 value)
		{
			base.CheckKey(key);
			client.SetCounter(key, value);
		}

		/// <summary>
		/// 获取计数器的值
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <returns>
		/// 如果该计数器不存在则返回null;数据转换错误将会直接throw异常;返回类型是UInt64类型
		/// </returns>
		public UInt64? GetCounter(String key)
		{
			base.CheckKey(key);
			return client.GetCounter(key);
		}

		/// <summary>
		/// 计数器 加+1操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <returns>
		/// 如果该计数器不存在则返回null;数据转换错误将会直接throw异常
		/// </returns>
		public Int32? Increment(String key)
		{
			return Helper.SafeConvertToNullableInt32(Increment(key, 1UL));
		}

		/// <summary>
		/// 计数器 加+int操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <returns>
		/// 如果该计数器不存在则返回null;数据转换错误将会直接throw异常
		/// </returns>
		public Int32? Increment(String key, Int32 value)
		{
			return Helper.SafeConvertToNullableInt32(Increment(key, Helper.ConvertInt32ToUInt64(value)));
		}

		/// <summary>
		/// 计数器 加+ulong操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <returns>
		/// 如果该计数器不存在则返回null;数据转换错误将会直接throw异常
		/// </returns>
		/// <remarks>
		/// C#中的调用方式为Increment("key", 123UL);
		/// </remarks>
		public UInt64? Increment(String key, UInt64 value)
		{
			base.CheckKey(key);
			return client.Increment(key, value);
		}

		/// <summary>
		/// 计数器 加-1操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <returns>
		/// 如果该计数器不存在则返回null;数据转换错误将会直接throw异常
		/// </returns>
		public Int32? Decrement(String key)
		{
			return Helper.SafeConvertToNullableInt32(Decrement(key, 1UL));
		}

		/// <summary>
		/// 计数器 加-int操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <returns></returns>
		public Int32? Decrement(String key, Int32 value)
		{
			return Helper.SafeConvertToNullableInt32(Decrement(key, Helper.ConvertInt32ToUInt64(value)));
		}

		/// <summary>
		/// 计数器 加-ulong操作
		/// </summary>
		/// <param name="key">缓存key</param>
		/// <param name="value">要缓存的内容</param>
		/// <returns>
		/// 如果该计数器不存在则返回null;数据转换错误将会直接throw异常
		/// </returns>
		/// <remarks>
		/// C#中的调用方式为Decrement("key", 123UL);
		/// </remarks>
		public UInt64? Decrement(String key, UInt64 value)
		{
			base.CheckKey(key);
			return client.Decrement(key, value);
		}

		#endregion

		#region 为方便开发添加的方法

		/// <summary>
		/// 获取缓存内容
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">缓存key</param>
		/// <returns></returns>
		public T Get<T>(String key)
		{
			var res = Get(key);
			if (res == null) return default(T);
			return (T)res;
		}

		#endregion
	}
}
